<html>
  <head>
    <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Event Loop | 🦌</title>
<link rel="shortcut icon" href="https://croatiaparanoia.github.io/blog//favicon.ico?v=1614759965596">
<link href="https://cdn.jsdelivr.net/npm/remixicon@2.3.0/fonts/remixicon.css" rel="stylesheet">
<link rel="stylesheet" href="https://croatiaparanoia.github.io/blog//styles/main.css">
<link rel="alternate" type="application/atom+xml" title="Event Loop | 🦌 - Atom Feed" href="https://croatiaparanoia.github.io/blog//atom.xml">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Droid+Serif:400,700">



    <meta name="description" content="任务分发器
一般的， 下面这些标签或者API都可以让一些任务间接的进入各自的队列。
任务：script、setTimeout、setInterval，DOM事件绑定， 请求响应等
微任务：promise
Event Loop 周期组成


..." />
    <meta name="keywords" content="Javascript" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.10.0/katex.min.css">
    <script src="https://cdn.bootcss.com/highlight.js/9.12.0/highlight.min.js"></script>
  </head>
  <body>
    <div class="main">
      <div class="main-content">
        <div class="site-header">
  <a href="https://croatiaparanoia.github.io/blog/">
  <img class="avatar" src="https://croatiaparanoia.github.io/blog//images/avatar.png?v=1614759965596" alt="">
  </a>
  <h1 class="site-title">
    🦌
  </h1>
  <p class="site-description">
    万物皆有裂痕，那是光照进来的地方。
  </p>
  <div class="menu-container">
    
      
        <a href="/blog/" class="menu">
          首页
        </a>
      
    
      
        <a href="/blog/archives" class="menu">
          归档
        </a>
      
    
      
        <a href="/blog/tags" class="menu">
          标签
        </a>
      
    
      
        <a href="/blog/post/about" class="menu">
          关于
        </a>
      
    
  </div>
  <div class="social-container">
    
      
        <a href="https://github.com/CroatiaParanoia" target="_blank">
          <i class="ri-github-line"></i>
        </a>
      
    
      
    
      
    
      
    
      
    
  </div>
</div>

        <div class="post-detail">
          <article class="post">
            <h2 class="post-title">
              Event Loop
            </h2>
            <div class="post-info">
              <span>
                2020-10-02
              </span>
              <span>
                18 min read
              </span>
              
                <a href="https://croatiaparanoia.github.io/blog/tag/5gGD3PI9U/" class="post-tag">
                  # Javascript
                </a>
              
            </div>
            
              <img class="post-feature-image" src="https://croatiaparanoia.github.io/blog//post-images/event-loop.jpg" alt="">
            
            <div class="post-content-wrapper">
              <div class="post-content">
                <h2 id="任务分发器">任务分发器</h2>
<p>一般的， 下面这些<strong>标签或者API</strong>都可以让一些任务<strong>间接</strong>的进入各自的队列。</p>
<p>任务：script、setTimeout、setInterval，DOM事件绑定， 请求响应等</p>
<p>微任务：promise</p>
<h2 id="event-loop-周期组成">Event Loop 周期组成</h2>
<ol>
<li>
<p>TASK 阶段 （重点就在这）</p>
<ul>
<li>任务调度和执行。 任务中代码执行完毕，调用栈清空后，开始执行微任务队列中的微任务。循环如此， 直至没有微任务，开始下一个任务。如果没有下一个任务， 则进入rAF。</li>
</ul>
</li>
<li>
<p>rAF(<strong>r</strong>equest<strong>A</strong>nimation<strong>F</strong>rame)阶段 - 所有任务执行完毕之后， 页面渲染之前。</p>
</li>
<li>
<p>Style  阶段 - 样式计算， 计算应用到元素上的样式。</p>
</li>
<li>
<p>Layout  阶段 - 创建渲染树， 找出页面上的所有内容及位置。</p>
</li>
<li>
<p>Paint  阶段 - 绘制内容到页面上。</p>
</li>
</ol>
<p>总的来分的话， 可以将上面五个阶段化为两个：</p>
<ol>
<li>TASK 阶段， 主要就是<strong>任务的调度和执行</strong>。 <code>Webapis</code> 推任务到任务队列，事件循环在合适的时候 将任务取出压入调用栈执行和弹出。</li>
<li>渲染绘制阶段，从2到4， 主要就是 绘制前的逻辑执行， 以及视图相关的绘制了。</li>
</ol>
<p>注：微任务的执行，取决于调用栈。调用栈为空才会执行微任务。</p>
<figure data-type="image" tabindex="1"><img src="https://croatiaparanoia.github.io/blog//post-images/1609755671039.png" alt="轨迹图" loading="lazy"></figure>
<h2 id="个人理解的事件循环的一个流程">个人理解的事件循环的一个流程</h2>
<ol>
<li>事件循环不断的判断 任务队列是否有值， 并且调用栈为空。（同时会在每隔16毫秒左右去进行一次视图绘制）</li>
<li><code>Webapis</code>  得到任务， 并在合适的时候将任务推入任务队列。</li>
<li>当有任务队列有值， 并且调用栈查询到的状态为空。则从任务队列中<strong>取出第一个任务</strong>， 压入调用栈，并开始执行该任务。（此时该任务在调用栈最底部）
<ol>
<li>任务代码执行时， 可能会分发新的任务， 比如设置定时器， 发起网络请求，监听事件这些操作。这些都会交与 <code>Webapis</code> 。 当定时器时间满足， 请求响应回来， 监听到事件这些时候，***<code>Webapis</code>  会在下一轮事件循环， 将这些满足条件的任务推入任务队列，等待事件循环的调度。***</li>
<li>任务代码执行时， 可能会分发新的微任务， 如 Promise.resolve().then(cb) 这种的， 此时，将cb函数交推入微任务队列。等待事件循环的调度。</li>
<li>当该任务中代码执行完毕后， 弹出任务，调用栈清空。</li>
<li>判断微任务队列是否有值， 如果有，则从中取出第一个微任务， 压入调用栈并执行，执行完毕 弹出。（反复此操作， 直至微任务队列为空）</li>
<li>当微任务队列为空， 且调用栈已清空时， 则开始下一个任务。</li>
</ol>
</li>
<li>重复第三条，直至任务队列为空， 且调用栈为空。</li>
<li>进入 渲染绘制 阶段， 依次调用 requestAnimationFrame 队列中的任务（此任务中进行分发任务和微任务， 参考3-1 到 3-5）。然后进入 <strong>Style Calculation</strong>、<strong>Layout Calculation</strong>、<strong>Paint</strong> 阶段。完成后开始新的一轮事件循环。（对于新的一轮事件循环判定的标准是 <strong>走过了 渲染绘制 阶段</strong>）</li>
<li>每轮 事件循环 开始时， 如果调用栈为空并且任务队列有值， 就会走上面的 第三到第五条的流程。</li>
<li>如果调用栈为空， 并且任务队列也为空， 则事件循环会进入空转状态（每隔16毫秒左右进行一次视图绘制， 即 每隔16ms 会走一次第五条）。</li>
</ol>
<h2 id="测试demo">测试Demo</h2>
<h3 id="1-由-settimeout-分发出来的任务">① 由 setTimeout 分发出来的任务</h3>
<pre><code class="language-javascript">console.log(&quot;glob1&quot;);

setTimeout(function timeout1Cb() {
  console.log(&quot;timeout1&quot;);
  new Promise(function timeout1PromiseCb(resolve) {
    console.log(&quot;timeout1_promise&quot;);
    resolve();
  }).then(function timeout1ThenCb() {
    console.log(&quot;timeout1_then&quot;);
  });
});

new Promise(function promise1Cb(resolve) {
  console.log(&quot;glob1_promise&quot;);
  resolve();
}).then(function promise1ThenCb() {
  console.log(&quot;glob1_then&quot;);
});

setTimeout(function timeout2Cb() {
  console.log(&quot;timeout2&quot;);
  new Promise(function timeout2PromiseCb(resolve) {
    console.log(&quot;timeout2_promise&quot;);
    resolve();
  }).then(function timeout2ThenCb() {
    console.log(&quot;timeout2_then&quot;);
  });
});

new Promise(function promise2Cb(resolve) {
  console.log(&quot;glob2_promise&quot;);
  resolve();
}).then(function promise2ThenCb() {
  console.log(&quot;glob2_then&quot;);
});

/**
解析：
1. 代码开始执行， 第一个 log 是 &quot;glob1&quot; 无疑。
2. 执行 `setTimeout(timeout1Cb)`， 将 `timeout1Cb` 交与 ` `Webapis` `， 等待 ` `Webapis` ` 在下一轮事件循环推入 任务队列。
3. 实例化 promise， 传入 promise1Cb 执行器，执行 promise1Cb。输出 &quot;glob1_promise&quot;
4. 调用 `resolve();` 往微任务队列中推了一个任务 `promise1ThenCb`。
5. 执行 `setTimeout(timeout2Cb)`， 将 `timeout2Cb` 交与 ` `Webapis` `， 等待 ` `Webapis` ` 在下一轮事件循环推入 任务队列。
6. 实例化 promise， 传入 promise2Cb 执行器，执行 promise2Cb。输出 &quot;glob2_promise&quot;
7. 调用 `resolve();` 往微任务队列中推了一个任务 `promise2ThenCb`。
8. 到此， 代码执行完毕，调用栈清空。并询问微任务队列是否有值。（此时微任务队列中应该是： [promise1ThenCb, promise2ThenCb]）。
9. 调用栈清空情况下， 取出微任务队列中的第一个任务 `promise1ThenCb`，压入调用栈中，并执行，输出 &quot;glob1_then&quot;。执行完毕， 调用栈弹出 `promise1ThenCb`。（此时调用栈为空， 微任务队列为: [promise2ThenCb]）。
10. 取出微任务队列中的第一个任务 `promise2ThenCb`，压入调用栈中，并执行，输出 &quot;glob2_then&quot;。执行完毕， 调用栈弹出 `promise2ThenCb`。（此时调用栈为空， 微任务队列为: []）。
- 至此， 第一轮事件循环的 TASK阶段 结束， 进入渲染绘制阶段。
  - 第一轮事件循环，控制台的log应该为： 	
  		&quot;glob1&quot;、&quot;glob1_promise&quot;、&quot;glob2_promise&quot;、&quot;glob1_then&quot;、&quot;glob2_then&quot;
 
--- 绘制完成， 开始第二轮事件循环 ---
1.  `Webapis` 将上个循环中的 `timeout1Cb` 和 `timeout2Cb`依次放入任务队列。（为什么会在第二轮循环就放到任务队列了， 是因为 setTimeout 第二个参数没传, 默认为0，就放到下一个循环中去执行了， 如果第二个参数是一个大于16的数， 那这个任务就可能会到下下个循环中去执行）
2. 在调用栈为空的情况下， 取出第一个任务 `timeout1Cb`， 压入调用栈中， 并执行， 输出 &quot;timeout1&quot;
3. 实例化 promise， 传入 `timeout1PromiseCb` 执行器，执行 `timeout1PromiseCb`。输出 &quot;timeout1_promise&quot;
4. 调用 `resolve();` 往微任务队列中推了一个任务 `timeout1ThenCb`。
5. 到此， `timeout1Cb` 代码执行完毕，调用栈清空。并询问微任务队列是否有值。（此时微任务队列中应该是： [timeout1ThenCb]）。
6. 调用栈清空情况下， 取出微任务队列中的第一个任务 `timeout1ThenCb`，压入调用栈中，并执行，输出 &quot;timeout1_then&quot;。执行完毕， 调用栈弹出 `timeout1ThenCb`。（此时调用栈为空， 微任务队列为: []）。
	- 至此， 第二轮事件循环的第一个任务执行完毕(目前任务队列： [timeout2Cb])

7. 在调用栈为空的情况下， 取出第一个任务 `timeout2Cb`， 压入调用栈中， 并执行， 输出 &quot;timeout2&quot;
8. 实例化 promise， 传入 `timeout2PromiseCb` 执行器，执行 `timeout2PromiseCb`。输出 &quot;timeout2_promise&quot;
9. 调用 `resolve();` 往微任务队列中推了一个任务 `timeout2ThenCb`。
10. 到此， `timeout2Cb` 代码执行完毕，调用栈清空。并询问微任务队列是否有值。（此时微任务队列中应该是： [timeout2ThenCb]）。
11. 调用栈清空情况下， 取出微任务队列中的第一个任务 `timeout2ThenCb`，压入调用栈中，并执行，输出 &quot;timeout2_then&quot;。执行完毕， 调用栈弹出 `timeout2ThenCb`。（此时调用栈为空， 微任务队列为: []）。
	- 至此， 第二轮事件循环的第二个任务执行完毕(目前任务队列： [])

- 至此， 第二轮事件循环的 TASK阶段 结束， 开始进入 渲染部分， 依次走 rAF， Style, Layout, Paint。
  - 第二轮事件循环，控制台的log应该为： 
  		&quot;timeout1&quot;、&quot;timeout1_promise&quot;、&quot;timeout1_then&quot;、&quot;timeout2&quot;、&quot;timeout2_promise&quot;、&quot;timeout2_then&quot;
  
--- 绘制完成， 开始第三轮事件循环 ---
1.  `Webapis`  上没有东西交给 任务队列， 所以此时 任务队列为空， 调用栈为空。于是事件循环进入空转状态。并每隔16ms左右 渲染一次页面。


**/

</code></pre>
<h3 id="2-由script-标签分发出来的任务">② 由script 标签分发出来的任务</h3>
<pre><code class="language-html">&lt;script&gt;
  console.log(&quot;script1 - begin&quot;);

  setTimeout(function cb1() {
    console.log(&quot;script1 - timeout1&quot;);
  });

  Promise.resolve().then(function microCb1(){
    console.log(&quot;script1 - then&quot;)
  });

  console.log(&quot;script1 - end&quot;);
&lt;/script&gt;
&lt;script&gt;
  console.log(&quot;script2 - begin&quot;);

  setTimeout(function cb2() {
    console.log(&quot;script2 - timeout1&quot;);
  });

  Promise.resolve().then(function microCb2(){
    console.log(&quot;script2 - then&quot;)
  });

  console.log(&quot;script2 - end&quot;);
&lt;/script&gt;


&lt;!-- 

解析： 
 ----- 第一轮事件循环 ------ 
1. 初始化js环境，将两个script的任务由  `Webapis`  推入 任务队列。（此时任务队列中有两个任务， 分别为对应的的script）
2. 询问调用栈是否为空， 是， 则取任务队列最前面一个， 压入调用栈执行， 任务队列length减1。
3. 执行第一个 script 中的代码。
4. 执行 `console.log(&quot;script1 - begin&quot;);`， 输出 &quot;script1 - begin&quot;
5. 执行 setTimeout 逻辑， 将cb1交与 `Webapis` , 并让 `Webapis` 在满足条件的情况下将cb1推入任务队列。此时是 0 毫秒后，及下一轮事件循环， 也就是当前事件循环结束后。
6. 执行 Promise.resolve 语句， 并向微任务队列推入一个 `microCb1` 的任务
7. 执行 `console.log(&quot;script1 - end&quot;);`， 输出 &quot;script1 - end&quot;
8. 此时代码执行完毕， 询问 调用栈是否为空 的答案为 true， 则开始执行微任务。
9. 取出微任务队列中的第一个任务 microCb1， 压入调用栈， 开始执行。输出 &quot;script1 - then&quot;， microCb1 执行完毕， 弹出调用栈。（如若还有， 继续重复此操作， 直到微任务队列为空）
10. 微任务此时length为0， 调用栈为空。
  - 此任务执行完毕， 依次输出 &quot;script1 - begin&quot;、&quot;script1 - end&quot; 、&quot;script1 - then&quot;

11. 开始执行下一个任务
12. 询问调用栈是否为空， 是， 则取队列最前面一个， 压入调用栈执行， 任务队列length减1， 则当前任务队列length为0。
13. ... 重复 3 - 10 ... 
 - 此任务执行完毕， 依次输出 &quot;script2 - begin&quot;、&quot;script2 - end&quot; 、&quot;script2 - then&quot;

14. 此时，调用栈为空， 且 任务队列为空。于是便走 渲染 阶段， 依次经过 requestAnimationFrame、 Style Calculation、Layout Calculation、 Pint 阶段（看下面 Event Loop 周期组成）
15. 直到 页面绘制完成， 这第一轮 事件循环 算是结束。于是便开始第二轮事件循环

 ----- 第二轮事件循环 ------ 

16. 我们在 script1 和 script2 中有向 `Webapis` 中分发了一个 cb1和 cb2 ， 在上一轮事件循环中它们一直处于  `Webapis` 中， 未到达任务队列。而此时，  `Webapis` 则会把这两个任务交给 任务队列。
17. 询问调用栈是否为空， 得到回复是 true， 则将任务队列中的 cb1 取出， 压入调用栈。（队列的顺序和分发时候的先后顺序保持一致）
18. 执行 cb1， 输出 &quot;script1 - timeout1&quot;。此时任务执行完毕， 调用栈清空。开始执行微任务， 发现没有微任务。OK， 执行下一个任务。
19. ... 重复 17 - 18 两个步骤, 把调用的 cb1 换成了cb2 ...
 - 上面两个任务执行完毕， 依次输出 &quot;script1 - timeout1&quot; 、&quot;script2 - timeout1&quot;

20. 此时，调用栈为空，且任务全部执行完毕（微任务没执行完毕是不会走下一个任务， 所以此处任务执行完毕的前提条件就是微任务全部执行完毕）
21. ... 重复 14 - 15 两个步骤， 至此， 所有任务执行完毕， 事件循环进行空转（虽说是空转， 其实也是每次都会去判断任务队列是否有值， 当有值并且调用栈为空的时候， 就去进行对应的操作， 取任务， 压栈， 执行，出栈...），并每隔 16.6ms 进行一次视图绘制。...


--&gt;

</code></pre>
<h3 id="3-由事件分发的任务-1">③ 由事件分发的任务 - 1</h3>
<pre><code class="language-html">... 
&lt;button id=&quot;btn&quot;&gt;
  按钮
&lt;/button&gt;


&lt;script&gt;
const btn = document.querySelector('#btn');

btn.addEventListener('click', function click1Cb(){
  Promise.resolve().then(function click1ThenCb() {
    console.log('promise1_then')
  })
  console.log('listener1');
})

btn.addEventListener('click', function click2Cb(){
  Promise.resolve().then(function click2ThenCb() {
    console.log('promise2_then')
  })
  console.log('listener2');
})

&lt;/script&gt;

&lt;!--
解析：
1. 获取btn元素。
2. 给btn绑定click事件， 即 把 `click1Cb` 函数交给  `Webapis` ， 当用户点击了 btn 后，  `Webapis` 会将这个 `click1Cb` 推入任务队列。
3. 给btn绑定click事件， 即 把 `click2Cb` 函数交给  `Webapis` ， 当用户点击了 btn 后，  `Webapis` 会将这个 `click2Cb` 推入任务队列。
4. 第一轮事件循环完毕， 进行空转。


--- 一段时间后 ---
用户点击这个按钮。
1.  `Webapis`  得知按钮点击， 便将 `click1Cb` 、`Click2Cb` 依次放入 任务队列中。
2. 事件循环判断当前调用栈是否为空，以及 任务队列 中是否有值。得到true 的结果后，取出任务队列中的第一个任务 `click1Cb`，压入调用栈中，并执行。
3. 执行 `click1Cb`， 首先 分发了一个微任务 `click1ThenCb` 到微任务队列中。接着走下一行的console.log， 输出 &quot;listener1&quot;
4. `click1Cb` 执行完毕， 调用栈弹出 `click1Cb`， 开始执行微任务， 此时微任务队列为： [click1ThenCb]
5. 取出微任务队列中的第一个微任务 `click1ThenCb`， 压入调用栈中， 并执行。输出 &quot;promise1_then&quot;。`click1ThenCb` 执行完毕， 调用栈弹出 `click1ThenCb`
- 至此，调用栈为空， 微任务队列为空。 第一个任务队列中的第一个任务执行完毕。

1. 取出 任务队列 的第一个任务 `click2Cb`，压入调用栈中，并执行。
2. 执行 `click2Cb`， 首先 分发了一个微任务 `click2ThenCb` 到微任务队列中。接着走下一行的console.log， 输出 &quot;listener2&quot;
3. `click2Cb` 执行完毕， 调用栈弹出 `click2Cb`， 开始执行微任务， 此时微任务队列为： [click2ThenCb]
4. 取出微任务队列中的第一个微任务 `click2ThenCb`， 压入调用栈中， 并执行。输出 &quot;promise2_then&quot;。`click2ThenCb` 执行完毕， 调用栈弹出 `click2ThenCb`
5. 至此，调用栈为空， 微任务队列为空。 第一个任务队列中的第一个任务执行完毕。

综上分析， 此demo的输出就是 &quot;listener1&quot;、 &quot;promise1_then&quot;、&quot;listener2&quot;、&quot;promise2_then&quot;
--&gt;



</code></pre>
<h3 id="4-由事件分发的任务-2">④ 由事件分发的任务 - 2</h3>
<pre><code class="language-html">... 
&lt;button id=&quot;btn&quot;&gt;
  按钮
&lt;/button&gt;


&lt;script&gt;
const btn = document.querySelector('#btn');

btn.addEventListener('click', function click1Cb(){
  Promise.resolve().then(function click1ThenCb() {
    console.log('promise1_then')
  })
  console.log('listener1');
})

btn.addEventListener('click', function click2Cb(){
  Promise.resolve().then(function click2ThenCb() {
    console.log('promise2_then')
  })
  console.log('listener2');
})
  
btn.click();

&lt;/script&gt;

&lt;!--
解析：
1. 获取btn元素。
2. 给btn绑定click事件， 即 把 `click1Cb` 函数交给  `Webapis` ， 当用户点击了 btn 后，  `Webapis` 会将这个 `click1Cb` 推入任务队列。
3. 给btn绑定click事件， 即 把 `click2Cb` 函数交给  `Webapis` ， 当用户点击了 btn 后，  `Webapis` 会将这个 `click2Cb` 推入任务队列。
4. btn 自己触发 click 事件, 调用栈压入 btn.click
5. 内部创建事件对象，缺少的数据由默认值填充。（如 鼠标位置信息等）
6. 依次触发事件（注意， 此次是内部js触发， 相当于自己注册事件，自己触发， 不走 `Webapis` ， 更不走任务队列）
7. 调用栈压入 `click1Cb`，执行 `click1Cb`， 此时调用栈：[ btn.click, click1Cb ]
8. 执行 `click1Cb`， 首先 分发了一个微任务 `click1ThenCb` 到微任务队列中。接着走下一行的console.log， 输出 &quot;listener1&quot;。(此时 微任务队列内容为： [click1ThenCb])
9. `click1Cb` 执行完毕， 调用栈弹出 `click1Cb`，此时调用栈内容为：[btn.click]， 因为调用栈非空， 不能执行微任务， 所以， 进行下一个事件监听函数的触发。 
10. 调用栈压入 `click2Cb`，执行 `click2Cb`。此时调用栈：[ btn.click, click2Cb ]
11. 执行 `click2Cb`， 首先 分发了一个微任务 `click2ThenCb` 到微任务队列中。接着走下一行的console.log， 输出 &quot;listener2&quot;。(此时 微任务队列内容为： [click1ThenCb, click2ThenCb])
12. `click2Cb` 执行完毕， 调用栈弹出 `click2Cb`，调用栈内容为：[btn.click]， 此时没有未触发的事件监听函数了， 所以 btn.click 执行完毕， 调用栈弹出 btn.click， 此时调用栈内容为：[]。 
13. 询问调用栈是否为空，并且是否有未执行的微任务。 得到结果为true。便开始执行微任务。
14. 取出微任务队列中的第一个微任务 `click1ThenCb`， 压入调用栈中， 并执行。输出 &quot;promise1_then&quot;。`click1ThenCb` 执行完毕， 调用栈弹出 `click1ThenCb`。
15. 进行下一个微任务执行， 取出微任务队列中的第一个微任务 `click2ThenCb`， 压入调用栈中， 并执行。输出 &quot;promise2_then&quot;。`click2ThenCb` 执行完毕， 调用栈弹出 `click2ThenCb`。此时， 微任务队列执行完毕。
16. 此时，任务队列为空， 且调用栈为空，便进入 渲染阶段， 渲染结束， 第一轮事件循环完毕。开始后面的事件循环。


综上分析， 此demo的输出就是 &quot;listener1&quot;、 &quot;listener2&quot;、&quot;promise1_then&quot;、&quot;promise2_then&quot;
--&gt;



</code></pre>
<p>两个 事件相关的demo总结：</p>
<p>其实第一个和第二个， 代码层面上看， 不同的就是第一个是需要用户与UI交互进行触发， 第二个是通过js触发。</p>
<p>但是从代码执行的时候看， 就会有这么一些区别：</p>
<ol>
<li>通过 UI 交互进行触发， 就会走 <code>Webapis</code> ， 因为dom 的事件监听都是通过 <code>Webapis</code> 来的。而通过 <code>Webapis</code> ， 在事件触发的时候，  <code>Webapis</code> 则会把事件处理函数推入 任务队列， 等待事件循环的调度与执行。</li>
<li>通过js触发， 这里就相当于一个简单的事件系统，一边去 监听， 一边去触发， 同步的操作，没涉及到异步， 更没涉及到 <code>Webapis</code> ， 任务队列。所以， 在触发事件处理函数的时候，调用栈一直都会有一个 btn.click 函数，等到事件处理函数全部执行完毕， 有了返回值。而 btn.click 才算执行结束。并从 调用栈弹出。清空了调用栈， 才能去执行 事件处理函数中 分发的微任务。</li>
</ol>
<p>到此， 事件循环我个人的理解就是这样。这也是我花了几天时间集中精力去理解得来的一个总结。希望对大家能有一些帮助。</p>
<p>另因为这个只是我个人理解， 所以各位看官看的时候当个参考即可。如果发现我的理解和你们的理解有些出入的， 也欢迎在评论里提出来，一起完善各自对 Event Loop 的理解。</p>
<h2 id="参考资料">参考资料</h2>
<ol>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth">深入：微任务与Javascript运行时环境</a></li>
<li><a href="https://www.jianshu.com/p/12b9f73c5a4f">前端基础进阶（十四）：深入核心，详解事件循环机制</a></li>
<li><a href="https://www.bilibili.com/video/BV1bE411B7ez">【熟肉 | 内核】深入JavaScript中的EventLoop</a></li>
<li><a href="https://www.bilibili.com/video/BV1oV411k7XY">【自制熟肉】Philip Roberts：到底什么是Event Loop呢？（JSConf EU 2014）</a></li>
</ol>

              </div>
              <div class="toc-container">
                <ul class="markdownIt-TOC">
<li>
<ul>
<li><a href="#%E4%BB%BB%E5%8A%A1%E5%88%86%E5%8F%91%E5%99%A8">任务分发器</a></li>
<li><a href="#event-loop-%E5%91%A8%E6%9C%9F%E7%BB%84%E6%88%90">Event Loop 周期组成</a></li>
<li><a href="#%E4%B8%AA%E4%BA%BA%E7%90%86%E8%A7%A3%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF%E7%9A%84%E4%B8%80%E4%B8%AA%E6%B5%81%E7%A8%8B">个人理解的事件循环的一个流程</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95demo">测试Demo</a>
<ul>
<li><a href="#1-%E7%94%B1-settimeout-%E5%88%86%E5%8F%91%E5%87%BA%E6%9D%A5%E7%9A%84%E4%BB%BB%E5%8A%A1">① 由 setTimeout 分发出来的任务</a></li>
<li><a href="#2-%E7%94%B1script-%E6%A0%87%E7%AD%BE%E5%88%86%E5%8F%91%E5%87%BA%E6%9D%A5%E7%9A%84%E4%BB%BB%E5%8A%A1">② 由script 标签分发出来的任务</a></li>
<li><a href="#3-%E7%94%B1%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E7%9A%84%E4%BB%BB%E5%8A%A1-1">③ 由事件分发的任务 - 1</a></li>
<li><a href="#4-%E7%94%B1%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E7%9A%84%E4%BB%BB%E5%8A%A1-2">④ 由事件分发的任务 - 2</a></li>
</ul>
</li>
<li><a href="#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99">参考资料</a></li>
</ul>
</li>
</ul>

              </div>
            </div>
          </article>
        </div>

        
          <div class="next-post">
            <div class="next">下一篇</div>
            <a href="https://croatiaparanoia.github.io/blog/post/xuan-ran-yu-xiao-yan/">
              <h3 class="post-title">
                渲染与校验
              </h3>
            </a>
          </div>
        

        
          
            <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>

<div id="gitalk-container"></div>

<script>

  var gitalk = new Gitalk({
    clientID: '344de4a7a0e3121bcdbc',
    clientSecret: 'cf3ede59043d5bf3f50b37b93791396bb4d1267e',
    repo: 'blog',
    owner: 'CroatiaParanoia',
    admin: ['CroatiaParanoia'],
    id: (location.pathname).substring(0, 49),      // Ensure uniqueness and length less than 50
    distractionFreeMode: false  // Facebook-like distraction free mode
  })

  gitalk.render('gitalk-container')

</script>

          

          
        

        <div class="site-footer">
  Powered by <a href="https://github.com/getgridea/gridea" target="_blank">Gridea</a>
  <a class="rss" href="https://croatiaparanoia.github.io/blog//atom.xml" target="_blank">
    <i class="ri-rss-line"></i> RSS
  </a>
</div>

      </div>
    </div>

    <script>
      hljs.initHighlightingOnLoad()

      let mainNavLinks = document.querySelectorAll(".markdownIt-TOC a");

      // This should probably be throttled.
      // Especially because it triggers during smooth scrolling.
      // https://lodash.com/docs/4.17.10#throttle
      // You could do like...
      // window.addEventListener("scroll", () => {
      //    _.throttle(doThatStuff, 100);
      // });
      // Only not doing it here to keep this Pen dependency-free.

      window.addEventListener("scroll", event => {
        let fromTop = window.scrollY;

        mainNavLinks.forEach((link, index) => {
          let section = document.getElementById(decodeURI(link.hash).substring(1));
          let nextSection = null
          if (mainNavLinks[index + 1]) {
            nextSection = document.getElementById(decodeURI(mainNavLinks[index + 1].hash).substring(1));
          }
          if (section.offsetTop <= fromTop) {
            if (nextSection) {
              if (nextSection.offsetTop > fromTop) {
                link.classList.add("current");
              } else {
                link.classList.remove("current");    
              }
            } else {
              link.classList.add("current");
            }
          } else {
            link.classList.remove("current");
          }
        });
      });

    </script>
  </body>
</html>
